package uloha4.main;

import uloha4.entities.Component;
import uloha4.entities.ComponentList;
import uloha4.entities.Node;
import uloha4.entities.Point;
// http://cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf

public class GraphLayout {

    static float k = 0;
    static int area;
    static float attractionConst = 16;//12
    static boolean ALLOWED_SHIFT = false;

    private static void setK(int countVerteces) {
        k = (float) Math.sqrt(area / countVerteces);
    }

    private static float getAttractiveForce(float x) {
        return (attractionConst * (x * x) / k);
    }

    private static float getRepulsiveForce(float x) {
        return ((k * k) / x);
    }

    public static void FruchtermanRheingoldLayout(ComponentList components,
            int W, int H, int iterations, float tStart) {

        area = W * H;

        Point delta;
        float deltaSize;
        float dispSize;
        float forceConst;
        float t;
        for (Component<Node> comp : components) {

            delta = new Point(0, 0);
            deltaSize = 0;
            dispSize = 0;
            forceConst = 0;
            t = tStart;
            setK(comp.getNodes());

            for (int i = 0; i < iterations; i++) {
                for (Node v : comp) {
                    v.disp = new Point(0, 0);

                    for (Node u : comp) {

                        if (!u.equals(v)) {
                            delta = MyMath.minus(v.pos, u.pos);
                            deltaSize = MyMath.size(delta);
                            if (deltaSize == 0 && ALLOWED_SHIFT) {
                                delta = new Point((int) (0.01 * W), (int) (0.01 * W));
                                deltaSize = (float) (0.01 * W);
                            }
                            forceConst = getRepulsiveForce(deltaSize) / deltaSize;
                            v.disp = MyMath.plus(v.disp, MyMath.times(delta, forceConst));
                        }
                    }
                }

                for (Node v : comp) {

                    for (Node u : v.getDirectedNeighbours()) {
                        delta = MyMath.minus(v.pos, u.pos);
                        deltaSize = MyMath.size(delta);
                        if (deltaSize == 0 && ALLOWED_SHIFT) {
                            delta = new Point((int) (0.01 * W), (int) (0.01 * W));
                            deltaSize = (float) (0.01 * W);
                        }
                        forceConst = getAttractiveForce(deltaSize) / deltaSize;
                        v.disp = MyMath.minus(v.disp, MyMath.times(delta, forceConst));
                        u.disp = MyMath.plus(u.disp, MyMath.times(delta, forceConst));
                    }
                }

                for (Node v : comp) {
                    dispSize = MyMath.size(v.disp);
//					System.out.println("Point position before: "+v.pos);
//					System.out.println("Point display: "+v.disp);
                    forceConst = Math.min(dispSize, t) / dispSize;
                    v.pos = MyMath.plus(v.pos, MyMath.times(v.disp, forceConst));
                    v.pos.x = Math.min(W / 2, Math.max(-W / 2, v.pos.x));
                    v.pos.y = Math.min(H / 2, Math.max(-H / 2, v.pos.y));
//					System.out.println("Point position after: "+v.pos);
                }

                t = cool(t);

            }

        }

    }

    public static void FruchtermanRheingold2Layout(ComponentList components,
            int W, int H, int iterations, float t) {

        area = W * H;

        Point delta;
        float deltaSize;
        float dispSize;
        float forceConst;

        for (Component<Node> comp : components) {

            setK(comp.getNodes());

            for (int i = 0; i < iterations; i++) {

                for (Node v : comp) {
                    v.disp = new Point(0, 0);

                    for (Node u : comp) {

                        if (!u.equals(v)) {
                            delta = MyMath.minus(v.pos, u.pos);
                            deltaSize = MyMath.size(delta);
                            forceConst = getRepulsiveForce(deltaSize) / deltaSize;
                            v.disp = MyMath.plus(v.disp, MyMath.times(delta, forceConst));
                        }
                    }
                }

                for (Node v : comp) {

                    for (Node u : v.getDirectedNeighbours()) {
                        delta = MyMath.minus(v.disp, u.disp);
                        deltaSize = MyMath.size(delta);
                        forceConst = getAttractiveForce(deltaSize) / deltaSize;
                        v.disp = MyMath.minus(v.disp, MyMath.times(delta, forceConst));
                        u.disp = MyMath.plus(u.disp, MyMath.times(delta, forceConst));
                    }
                }

                for (Node v : comp) {
                    delta = MyMath.minus(v.disp, v.pos);
                    deltaSize = MyMath.size(delta);
//					System.out.println("Point position before: "+v.pos);
//					System.out.println("Point display: "+v.disp);
                    forceConst = Math.min(deltaSize, t) / deltaSize;
                    v.pos = MyMath.plus(v.pos, MyMath.times(delta, forceConst));
//					System.out.println("Point position after: "+v.pos);
                }

                t = cool(t);

            }

        }

    }

    private static float cool(float t) {
        // TODO Auto-generated method stub
        return 0.98f * t;
    }

    public static int getRandom(int lower, int upper) {
        return lower + (int) (Math.random() * (upper - lower + 1));
    }

    public static void randomLayout(ComponentList components, int width, int height) {

        // definuji obdelnik, do ktereho chci nahodne pozicovat
        int xMin = -width / 2;
        int xMax = width / 2;
        int yMin = -height / 2;
        int yMax = height / 2;

        for (Component<Node> comp : components) {

            for (Node node : comp) {
                node.pos.x = getRandom(xMin, xMax);
                node.pos.y = getRandom(yMin, yMax);
            }
        }
    }

    public static void gridLayout(ComponentList components, int width, int height) {
        // definuji obdelnik, do ktereho chci nahodne pozicovat
        int xMin = -width / 2;
        int xMax = width / 2;
        int yMin = -height / 2;
        int yMax = height / 2;

        int x = xMin;
        int y = yMin;

        for (Component<Node> comp : components) {
            for (Node node : comp) {
                node.pos.x = x;
                node.pos.y = y;

                x += 15;
                if (x > xMax) {
                    x = xMin;
                    y += 15;
                }
            }
        }
    }

    public static void circleLayout(ComponentList components, int width, int height) {
        // definuji obdelnik, do ktereho chci nahodne pozicovat
        int r = Math.max(width, height) / 2;
        float step;

        for (Component<Node> comp : components) {
            if (components.size() > 1) {
                step = (float) (2 * Math.PI / comp.getNodes());
                for (int i = 0; i < comp.size(); i++) {
                    comp.get(i).pos.x = (int) (r * Math.cos(i * step));
                    comp.get(i).pos.y = (int) (r * Math.sin(i * step));
                }
            }
        }
    }
}
